import os, sys, json
import argparse
import subprocess
import time
import shutil
from PySide6.QtCore import QProcess, QObject, QCoreApplication, Slot, QByteArray
from enum import Enum, auto

QT_PREFIX = r"C:/Qt5.15.12_arm64_5.0.3.135-15_20250722_openharmony.win/ohos15_arm64-v8a"

# 设备序列号
DEVICE_SERIAL = None


class TestItemEnum(Enum):
    """测试项定义

    枚举项对应entry/src/main/cpp/CMakeLists.txt里面定义的可选项

    # QtSerialBus
    option(TST_QTSERIALBUS_AUTO "Enable QTSERIALBUS_AUTO Test" OFF)

    # QtSerialPort
    option(TST_QTSERIALPORT_AUTO "Enable QTSERIALPORT_AUTO Test" OFF)

    """

    # QtSerialBus
    #    TST_QTSERIALBUS_AUTO = auto()

    # QtSerialPort
    #    TST_QTSERIALPORT_AUTO = auto()

    # Qt3D
    #    TST_QT3D_AUTO_ANIMATION = auto()
    #    TST_QT3D_AUTO_CORE = auto()
    # TST_QT3D_AUTO_EXTRAS = auto()             #only private test
    # TST_QT3D_AUTO_GEOMETRYLOADERS = auto()    #only private test
    # TST_QT3D_AUTO_INPUT = auto()              #only private test
    # TST_QT3D_AUTO_QUICK3D = auto()            #only private test
    # TST_QT3D_AUTO_RENDER = auto()             #only private test
    #    TST_QT3D_BENCHMARKS_CORE = auto()
    # TST_QT3D_BENCHMARKS_RENDER = auto()       #only private test

    # QtRemoteObjects
    #    TST_QTREMOTEOBJECTS_AUTO_BENCHMARKS = auto()
    #    TST_QTREMOTEOBJECTS_AUTO_INTEGRATION = auto()
    #    TST_QTREMOTEOBJECTS_AUTO_MODELREPLICA = auto()
    #    TST_QTREMOTEOBJECTS_AUTO_MODELVIEW = auto()
    #    TST_QTREMOTEOBJECTS_AUTO_PODS = auto()
    #    TST_QTREMOTEOBJECTS_AUTO_PROXY = auto()
    # TST_QTREMOTEOBJECTS_AUTO_PROXY_MULTIPROCESS = auto()        # 多进程鸿蒙支持 ??
    #    TST_QTREMOTEOBJECTS_AUTO_QML = auto()
    #    TST_QTREMOTEOBJECTS_AUTO_REPC = auto()
    #    TST_QTREMOTEOBJECTS_AUTO_REPCODEGENERATOR = auto()
    # TST_QTREMOTEOBJECTS_AUTO_REPPARSER = auto()                # private test
    # TST_QTREMOTEOBJECTS_AUTO_RESTART = auto()                   # 多进程鸿蒙支持 ??
    #    TST_QTREMOTEOBJECTS_AUTO_SUBCLASSREPLICA = auto()

    # QtCharts
    #    TST_QTCHARTS_AUTO = auto()

    # QtConnectivity
    #    TST_QTCONNECTIVITY_AUTO = auto()  #运行后程序卡在“等待程序运行结束”

    # QtImageFormats
    #    TST_QTIMAGEFORMATS_AUTO = auto()

    # QtMultimedia
    #    TST_QTMMULTIMEDIA_AUTO_INTEGRATION = auto()
    #    TST_QTMMULTIMEDIA_AUTO_UNIT_MULTIMEDIA = auto()

    # QtSensors
    #    TST_QTSSENSORS_AUTO = auto() #运行后程序卡在“等待远程程序com.example.qttestproject结束...”

    # QtSVG
    TST_QTSVG_AUTO = auto()

    # QtBase
    #    TST_QTBASE_AUTO_CORELIB = auto()
    #    TST_QTBASE_AUTO_GUI = auto()
    #    TST_QTBASE_AUTO_INTEGRATION_GUI = auto()
    #    TST_QTBASE_AUTO_INTEGRATION_NONE = auto()
    #    TST_QTBASE_AUTO_NETWORK = auto()
    #    TST_QTBASE_AUTO_OTHER = auto()
    #    TST_QTBASE_AUTO_RESIDUE = auto()
    #    TST_QTBASE_AUTO_TESTLIB = auto()
    #    TST_QTBASE_AUTO_WIDGETS = auto()
    #    TST_QTBASE_BENCHMARKS = auto()

    # QtGraphicalEffects
    TST_QTGRAPHICALEFFECTS_AUTO = auto()

    # QtDeclarative
    TST_QTDECLARATIVE_AUTO = auto()
    #    TST_QTDECLARATIVE_BENCHMARKS = auto()

    # QtDatavis3D
    TST_QTDATAVIS3D_AUTO = auto()
    TST_QTDATAVIS3D_AUTO2 = auto()
    TST_QTDATAVIS3D_AUTO3 = auto()
    TST_QTDATAVIS3D_AUTO4 = auto()

    # QtQuickControls2
    TST_QTQuickControls2_AUTO = auto()
    #    TST_QTQuickControls2_BENCHMARKS = auto()
    TST_QTQuickControls2_QQUICKICONIMAGE = auto()

    # QtLocation
    TST_QTLocation_AUTO = auto()
    TST_QTLocation_AUTO2 = auto()


def wrap_hdc_command(command: str) -> str:
    """包装hdc命令，添加设备序列号参数"""
    if DEVICE_SERIAL:
        # 在命令开头插入-t参数
        return (
            f"hdc -t {DEVICE_SERIAL} {command[4:]}"
            if command.startswith("hdc ")
            else f"hdc -t {DEVICE_SERIAL} {command}"
        )
    return command


def exec_order(
    order: str, desc: str, check_iserror_func=None, printlog: bool = False
):
    """执行命令

    Args:
        order (str): 命令
        printlog (bool): 是否打印日志
        desc (str): 命令描述
        checkoutput_func (function, optional): 从函数输出判断函数是否执行失败的函数(返回Bool型). Defaults to None.

    Raises:
        Exception: _description_
    """
    print(f"{desc}命令执行: {order}")
    output = subprocess.check_output(order, shell=True)
    if printlog:
        print(output.decode("utf-8"))
    if check_iserror_func and check_iserror_func(output):
        raise Exception(f"{desc}命令失败, 命令返回异常: {output.decode()}")


def before_build(projectpath: str, item: TestItemEnum):
    """构建之前的操作，替换测试项

    Args:
        projectpath (str): 工程路径
        item (TestItemEnum): 测试项
    """
    with open(
        os.path.join(projectpath, "entry/build-profile.json5"), "r", encoding="utf-8"
    ) as fr:
        jsondata = json.load(fr)
        # 修改CMAKE传参
        jsondata["buildOption"]["externalNativeOptions"][
            "arguments"
        ] = f"-DQT_PREFIX={QT_PREFIX} -D{item.name}=ON"
        with open(
            os.path.join(projectpath, "entry/build-profile.json5"),
            "w",
            encoding="utf-8",
        ) as fw:
            json.dump(jsondata, fw, indent=4, ensure_ascii=False)


def check_output_is_errorinfo(output: bytes) -> bool:
    """检测hdc输出的结果是否为错误结果

    Args:
        output (bytes): 输出

    Returns:
        bool: 是否为错误结果
    """
    return output.startswith(b"[Fail]") or output.startswith(b"error:")


def build_hap_file_path(projectpath: str) -> str:
    """构建hap文件路径

    Args:
        projectpath (str): 工程路径

    Returns:
        str: hap文件路径
    """
    return os.path.abspath(
        os.path.join(
            projectpath, "entry/build/default/outputs/default/entry-default-signed.hap"
        )
    )


def read_bundle_name(projectpath: str) -> str:
    """读取App的bundleName

    Args:
        projectpath (str): 工程路径

    Returns:
        str: App的bundleName
    """
    with open(
        os.path.join(projectpath, "AppScope/app.json5"), "r", encoding="utf-8"
    ) as f:
        jsondata = json.load(f)
        name = jsondata["app"]["bundleName"]
        if len(name) == 0:
            raise Exception("BundleName读取为空")
        return name


def build(projectpath: str, printlog: bool = False):
    """构建工程

    Args:
        projectpath (str): 工程路径
        printlog (bool, optional): 是否打印日志. Defaults to False.
    """
    hap_file_path = build_hap_file_path(projectpath=projectpath)

    if os.path.exists(hap_file_path):
        os.remove(hap_file_path)

    order = f"cd {projectpath} & hvigorw --mode module -p product=default assembleHap --parallel --incremental --daemon"
    print(f"构建命令执行: {order}")
    output = subprocess.check_output(order, shell=True)
    if printlog:
        print(output.decode("utf-8"))
    if b"BUILD SUCCESSFUL" not in output:
        raise Exception("构建失败")


def stop_remote_app(bundleName: str, printlog: bool = False):
    """停止远程App

    Args:
        bundleName (str): App的BundleName
        printlog (bool, optional): 是否打印日志. Defaults to False.
    """
    order = wrap_hdc_command(f"hdc shell aa force-stop {bundleName}")
    print(f"停止远程App命令执行: {order}")
    output = subprocess.check_output(order, shell=True)
    if printlog:
        print(output.decode("utf-8"))
    # if check_output_is_errorinfo(output):
    #     raise Exception(f"停止安装远程App失败, 命令返回异常: {output.decode()}")


def reinstall_remote_app(projectpath: str, printlog: bool = False):
    """重新安装远程APP

    Args:
        projectpath (str): 工程路径
        printlog (bool, optional): 是否打印日志. Defaults to False.
    """
    bundleName = read_bundle_name(projectpath=projectpath)
    stop_remote_app(bundleName=bundleName)
    remote_temp_path = "data/local/tmp/8ebf67055e718fd8cb7c3927104d902b"
    hap_file_path = build_hap_file_path(projectpath=projectpath)

    if not os.path.exists(hap_file_path):
        raise Exception(f"未找到文件: {hap_file_path}")

    exec_order(
        order=wrap_hdc_command(f"hdc shell bm uninstall -n {bundleName}"),
        desc=f"卸载远程APP",
        # check_iserror_func=check_output_is_errorinfo,
        printlog=True,
    )

    exec_order(
        order=wrap_hdc_command(f"hdc shell rm -rf {remote_temp_path}"),
        desc=f"删除远程缓存目录",
        # check_iserror_func=check_output_is_errorinfo,
        printlog=True,
    )

    exec_order(
        order=wrap_hdc_command(f"hdc shell mkdir {remote_temp_path}"),
        desc=f"创建远程缓存目录",
        # check_iserror_func=check_output_is_errorinfo,
        printlog=True,
    )

    exec_order(
        order=wrap_hdc_command(f"hdc file send {hap_file_path} {remote_temp_path}"),
        desc=f"发送HAP文件到远程缓存",
        check_iserror_func=check_output_is_errorinfo,
        printlog=True,
    )

    exec_order(
        order=wrap_hdc_command(f"hdc shell bm install -p {remote_temp_path}"),
        desc=f"安装远程App命令",
        check_iserror_func=check_output_is_errorinfo,
        printlog=True,
    )

    exec_order(
        order=wrap_hdc_command(f"hdc shell rm -rf {remote_temp_path}"),
        desc=f"删除远程缓存目录",
        check_iserror_func=check_output_is_errorinfo,
        printlog=True,
    )


def start_remote_app(
    projectpath: str, entryAbility: str = "EntryAbility", printlog: bool = False
):
    """启动远程App

    Args:
        projectpath (str): 工程路径
        entryAbility (str, optional): App的入口Ability. Defaults to "EntryAbility".
        printlog (bool, optional): 是否打印日志. Defaults to False.
    """
    bundleName = read_bundle_name(projectpath=projectpath)

    order = wrap_hdc_command(f"hdc shell aa start -a {entryAbility} -b {bundleName}")
    print(f"启动远程App命令执行: {order}")
    output = subprocess.check_output(order, shell=True)
    if printlog:
        print(output.decode("utf-8"))
    if check_output_is_errorinfo(output):
        raise Exception(f"启动远程App失败, 命令返回异常: {output.decode()}")


class SubprocessManager(QObject):
    def __init__(self, bundleName: str):
        super().__init__()

        if len(bundleName) > 0:
            if QCoreApplication.instance():
                self.app = QCoreApplication.instance()
            else:
                self.app = QCoreApplication()

            self.process = QProcess()
            self.bundleName = bundleName

            # 连接子进程的输出信号，实时获取标准输出
            self.process.readyReadStandardOutput.connect(self.__read_output)

            # 连接子进程的错误输出信号，实时获取标准错误输出
            self.process.readyReadStandardError.connect(self.__read_error)
        else:
            self.app = None
            self.process = None

    def exec(self):
        if self.app:
            # 根据设备序列号构建命令
            command = ["hdc"]
            if DEVICE_SERIAL:
                command.extend(["-t", DEVICE_SERIAL])
            command.append("track-jpid")

            self.process.start(command[0], command[1:])
            self.app.exec()

    @Slot()
    def __read_output(self):
        output = self.process.readAllStandardOutput()
        print(f"等待远程程序{self.bundleName}结束...")
        if self.bundleName not in bytes(output).decode():
            self.app.exit()

    @Slot()
    def __read_error(self):
        error = self.process.readAllStandardError()
        print("等待远程程序{self.bundleName}结束出现异常，", bytes(error).decode())


def track_remote_app(bundleName: str):
    """追踪远程App

    Args:
        bundleName (str): App的BundleName
    """
    time.sleep(1)
    track = SubprocessManager(bundleName=bundleName)
    track.exec()


# 远程结果文件路径
def build_remote_result_path(bundleName: str) -> str:
    """构建远程结果文件路径

    Args:
        bundleName (str): App的BundleName

    Returns:
        str: 远程结果文件路径
    """
    return f"/data/app/el2/100/base/{bundleName}/haps/entry/files"


# 移除远程测试结果文件
def remove_test_result_files(bundleName: str, printlog: bool = False):
    """删除远程结果文件路径

    Args:
        bundleName (str): App的BundleName
        printlog (bool, optional): 是否打印日志. Defaults to False.
    """
    remote_path = build_remote_result_path(bundleName=bundleName)
    order = wrap_hdc_command(f"hdc shell rm -rf {remote_path}")
    print(f"删除远程测试结果文件夹命令执行: {order}")
    output = subprocess.check_output(order, shell=True)
    if printlog:
        print(output.decode("utf-8"))
    if check_output_is_errorinfo(output):
        raise Exception(f"删除远程测试结果文件夹命令返回异常: {output.decode()}")


# 列出远程测试结果文件
def list_test_result_files(bundleName: str, printlog: bool = False) -> dict:
    """列出远程测试结果文件

    Args:
        bundleName (str): App的BundleName
        printlog (bool, optional): 是否打印日志. Defaults to False.

    Returns:
        dict: 结果文件字典
    """
    files = {}
    remote_path = build_remote_result_path(bundleName=bundleName)
    order = wrap_hdc_command(f"hdc shell ls {remote_path}")
    print(f"列出远程测试结果文件命令执行: {order}")
    output = subprocess.check_output(order, shell=True).strip()
    if printlog:
        print(output.decode("utf-8"))
    if check_output_is_errorinfo(output):
        raise Exception(f"列出远程测试结果文件命令返回异常: {output.decode()}")

    # if (output.startswith(b"Tst_") or output.startswith(b"tst_")) and output.endswith(b".xml"):
    if output.endswith(b".xml") or output.endswith(b".txt"):
        filenames = output.split(b"\n")
        for filename in filenames:
            files[filename.decode().strip()] = (
                f"{remote_path}/{filename.decode().strip()}"
            )

    print(f"获取到测试结果文件: {files}")
    return files


# 读取所有的测试结果到本地文件夹
def read_all_test_results(
    bundleName: str, output_dir: str = "test_results", printlog: bool = False
):
    """读取所有测试结果

    Args:
        bundleName (str): App的BundleName
        output_dir (str, optional): 输出路径目录. Defaults to "test_results".
        printlog (bool, optional): 是否打印日志. Defaults to False.
    """
    if os.path.exists(output_dir):
        shutil.rmtree(output_dir)

    if not os.path.exists(output_dir):
        os.makedirs(output_dir)
    remote_files = list_test_result_files(bundleName=bundleName, printlog=printlog)
    for filename, remotepath in remote_files.items():
        order = wrap_hdc_command(
            f"hdc file recv {remotepath} {os.path.join(output_dir, filename)}"
        )
        print(f"接收远程测试结果文件命令执行: {order}")
        output = subprocess.check_output(order, shell=True)
        if printlog:
            print(output.decode("utf-8"))
        if check_output_is_errorinfo(output):
            raise Exception(f"接收远程测试结果文件命令返回异常: {output.decode()}")


# 测试项测试
def test(projectpath: str, item: TestItemEnum, printlog: bool = False):
    """测试

    Args:
        projectpath (str): 工程路径
        item (TestItemEnum): 测试项
        printlog (bool, optional): 是否打印日志. Defaults to False.
    """
    print(f"\n-----------开始测试: {item.name}-----------")
    bundleName = read_bundle_name(projectpath=projectpath)
    before_build(projectpath=projectpath, item=item)
    build(projectpath=projectpath, printlog=printlog)
    # 后面有卸载app操作，该步骤可以省略。
    # remove_test_result_files(bundleName)
    stop_remote_app(bundleName=bundleName)
    reinstall_remote_app(projectpath=projectpath, printlog=printlog)
    start_remote_app(projectpath=projectpath, printlog=printlog)
    track_remote_app(bundleName=bundleName)
    read_all_test_results(
        bundleName=bundleName, output_dir=f"{root_test_results}/{item.name}"
    )
    print(f"-----------结束测试: {item.name}-----------\n")


if "__main__" == __name__:

    # 创建 ArgumentParser 对象
    parser = argparse.ArgumentParser(description="Qt鸿蒙单元测试脚本", add_help=True)

    # 添加命令行参数
    parser.add_argument(
        "path",
        type=str,
        help="工程目录",
    )

    # 添加设备序列号参数
    parser.add_argument(
        "-d", "--device", type=str, help="设备序列号（多设备连接时需要）", default=None
    )

    # 解析命令行参数
    args = parser.parse_args()

    # 仅在device参数有效时才赋值
    if args.device and args.device.strip():
        DEVICE_SERIAL = args.device.strip()
        print(f"使用指定设备序列号: {DEVICE_SERIAL}")
    else:
        print("未指定设备序列号，将使用默认设备")

    root_test_results = "test_results"

    if os.path.exists(root_test_results):
        shutil.rmtree(root_test_results)

    for item in TestItemEnum:
        test(projectpath=args.path, item=item, printlog=True)
